# -*-coding:utf-8-*-

import functools
from Crypto.Cipher import AES
from Crypto import Random
import base64, time
import json
from tornado.gen import coroutine, Return
from tornado.concurrent import Future

if '__main__' == __name__:
    import sys

    sys.path.append('..')
import settings

# AES_KEY = 'b11520a8dbe53dc9'


# def generate_token(user_id, group_id, expire_in=3600):
def generate_token(app, user_id, remote_ip, expires_in=3600, token_args={}):
    appkey = app.appkey
    AES_KEY = app.id_str
    iv = Random.new().read(AES.block_size)
    cipher = AES.new(AES_KEY, AES.MODE_CFB, iv)
    expires_at = time.time() + expires_in
    # plain_text = '%s:%s:%f' % (user_id, group_id, expire_at)
    remote_ip = base64.urlsafe_b64encode(remote_ip)
    token_args = base64.urlsafe_b64encode(json.dumps(token_args))
    plain_text = '%s:%s:%f:%s' % (user_id, remote_ip, expires_at, token_args)
    cipher_text = cipher.encrypt(plain_text)
    access_token = iv + str(appkey) + cipher_text
    return base64.urlsafe_b64encode(access_token), int(expires_at)


def parse_token(app, access_token):
    access_token = base64.urlsafe_b64decode(access_token)
    # iv = Random.new().read(AES.block_size)
    iv = access_token[:16]
    appkey = access_token[16:48]
    AES_KEY = app.id_str
    cipher_text = access_token[48:]
    cipher = AES.new(AES_KEY, AES.MODE_CFB, iv)
    plain_text = cipher.decrypt(cipher_text)
    plain_text = plain_text.split(':')
    # return plain_text[0], plain_text[1], float(plain_text[2])
    user_id = plain_text[0]
    remote_ip = base64.urlsafe_b64decode(plain_text[1])
    expires_at = float(plain_text[2])
    token_args = json.loads(base64.urlsafe_b64decode(plain_text[3]))
    return appkey, user_id, remote_ip, expires_at, token_args


# 已同时支持同步异步情况
# 因为对于异步的情况 authenticated 依赖 coroutine，所以 coroutine 需要出现在 authenticated 下面
# 示例如下：
# @authenticated
# @coroutine
# def func(...)
def authenticated(method):
    """Decorate methods with this to require that the user be logged in.

    If the user is not logged in, they will be redirected to the configured
    `login url <RequestHandler.get_login_url>`.

    If you configure a login url with a query parameter, Tornado will
    assume you know what you're doing and use it as-is.  If not, it
    will add a `next` parameter so the login page knows where to send
    you once you're logged in.
    """

    @functools.wraps(method)
    @coroutine
    def wrapper(self, *args, **kwargs):
        access_token = self.get_cookie('access_token', None)
        if access_token is None:
            access_token = self.get_argument('access_token', None)
            if access_token is None:
                self.respond_error('access_token_needed')
        access_token = str(access_token)
        appkey = base64.urlsafe_b64decode(access_token)[16:48]
        self.app = yield self.apps_dao.get_app(appkey)
        (appkey, user_id, remote_ip, expires_at, token_args) = parse_token(self.app, access_token)
        if expires_at <= time.time():
            self.respond_error('access_token_expired')
        if user_id != settings.root_user_id:
            if remote_ip != self.request.remote_ip:
                raise Exception('access denied, token ip: %s, request ip: %s' % (remote_ip, self.request.remote_ip))
        # cache info that frequently used
        self.access_token = access_token
        self.user = yield self.users_dao.get_user_by_id(self.app, user_id)
        self.app_id = self.app.id_str
        # self.user_id = self.user.id_str
        self.user_id = user_id
        self.token_args = token_args
        # check permissions
        if not self.check_permissions(self.app.get_permissions()):
            self.respond_error('permission_denied')
        # execute the logic of handler
        result = method(self, *args, **kwargs)
        if isinstance(result, Future):
            result = yield result
        raise Return(result)

    return wrapper


def test():
    result = generate_token('CA7E0BE302DF22F9CF4315BDCF66B62D', '5a587904d823ef4e582422e4', '127.0.0.1',
                            expires_in=3600 * 24 * 366)
    print result
    print parse_token(result[0])


if '__main__' == __name__:
    test()
